
#include "user_password.h"

static const user_dynpwd_instance_s 
	sc_user_dynpwd_instances[USER_DYNPWD_TYPE_MAX] = 
{
	USER_DYNPWD_INSTANCE_DEFINE(NOTS),
	USER_DYNPWD_INSTANCE_DEFINE(EDTQ),
	USER_DYNPWD_INSTANCE_DEFINE(SDED),
};

__RETMEM_USR user_dynpwd_info_s g_user_dynpwd_info;

user_dynpwd_s g_user_dynpwd = {
	.instance = {
		.type = GKT_USER_TYPE_DYNPWD,
		.flags = USER_INSTANCE_F_ITEM_HEADER_EXTEND,
		.c_ops = NULL,
		.c_partition_name = "UserDynpwd",

		.info = NULL,
		.auth_user_maxnums = { 0, 0, 0 },

		.item_size = USER_DYNPWD_ITEM_SIZE,
		.item_data_maxsize = 0,
		.item_extend_header = &g_user_dynpwd.save_item.ext_header,
	},
	.next_seq	= 0,
};

/************************************************************
* BPK Key:A total of 32 uint32_t 
*		  uint32_t [0~6]:Refer to gkt_retmem.h
*
*		  uint32_t [7]:user dynpwd info
*			   Byte[0]:
*			   Byte[1]:admin item indexs[pwd admin 0]
*			   Byte[2]:
*			   Byte[3]:admin item indexs[pwd admin 1]
*		  uint32_t [8]:user dynpwd info
*			   Byte[0]:
*			   Byte[1]:admin item indexs[pwd admin 2]
*			   Byte[2]:
*			   Byte[3]:admin item indexs[pwd admin 3]
*
*		  			   user dynpwd totp enable
*		  uint32_t [9]:totp_last_steps[password admin 0]
*		  uint32_t [10]:totp_last_steps[password admin 1]
*		  uint32_t [11]:totp_last_steps[password admin 2]
*		  uint32_t [12]:totp_last_steps[password admin 3]	
************************************************************/
static void user_dynpwd_save_bpk_info(uint32_t position)
{
	uint32_t value;

	if (position >= GKT_CONFIG_USER_PASSWORD_ADMIN_MAXNUMS) {
		gkt_error("pwd admin=%d>=%d err\n",position, g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN]);
		return;
	}

	if (position % 2) {
		G_BPK_ReadKey(&value, 1, GKT_RETMEM_USER_DYNPWD_INFO0_1 + (position/2));
		value &= 0x0000FFFF;
		value |= (g_user_dynpwd_info.admin_item_indexs[position] << 16);
		G_BPK_WriteKey(&value, 1, GKT_RETMEM_USER_DYNPWD_INFO0_1 + (position/2));
	}
	else {
		G_BPK_ReadKey(&value, 1, GKT_RETMEM_USER_DYNPWD_INFO0_1 + (position/2));
		value &= 0xFFFF0000;
		value |= g_user_dynpwd_info.admin_item_indexs[position];
		G_BPK_WriteKey(&value, 1, GKT_RETMEM_USER_DYNPWD_INFO0_1 + (position/2));
	}
	
#if USER_DYNPWD_TOTP_ENABLE
	value = g_user_dynpwd_info.totp_last_steps[position];
	G_BPK_WriteKey(&value, 1, GKT_RETMEM_USER_DYNPWD_ADMIN0_PWD + position);
#endif	
}
static void user_dynpwd_clear_info(uint32_t position)
{
	g_user_dynpwd_info.admin_item_indexs[position] = ~0;
#if USER_DYNPWD_TOTP_ENABLE
	g_user_dynpwd_info.totp_last_steps[position] = ~0;
#endif
	user_dynpwd_save_bpk_info(position);
}

int user_dynpwd_add_info(uint32_t item_index)
{
	const user_item_header_u *c_item_header;
	uint32_t nums;

	gkt_trace("user_dynpwd: add_info - item_index(%u)\n", item_index);
	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_error("user_dynpwd: add_info - not init!\n");
		return GKT_ENOTINIT;
	}

	c_item_header = USER_ITEM_HEADER_C(&g_user_password.instance, item_index);
	gkt_trace("user_dynpwd: add_info - user_index(%u), auth(%u)\n",
			c_item_header->user_index, c_item_header->authority.type);

	if (GKT_USER_AUTH_ADMIN == c_item_header->authority.type) {
		nums = (uint32_t)g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN];
		gkt_trace("user_dynpwd: add_info - nums = %u\n", nums);
		if (nums > GKT_USER_PASSWORD_ADMIN_MAXNUMS) {
			gkt_error("user_dynpwd_add_info: add_info - admin OVERFLOW!\n");
			return GKT_EOVERFLOW;
		}
		else if (!nums) {
			gkt_error("user_dynpwd_add_info: add_info - admin UNDERFLOW!\n");
			return GKT_EUNDERFLOW;
		}

		g_user_dynpwd_info.admin_item_indexs[nums- 1] = (uint16_t)item_index;
#if USER_DYNPWD_TOTP_ENABLE
		g_user_dynpwd_info.totp_last_steps[nums - 1] = 0;
#endif
		user_dynpwd_save_bpk_info(nums-1);
	}

	return GKT_SUCCESS;
}

void user_dynpwd_delete_info(uint32_t item_index)
{
	const user_item_header_u *c_item_header;
	uint32_t i, nums;

	gkt_trace("user_dynpwd: delete_info - item_index(%u)\n", item_index);
	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_error("user_dynpwd: delete_info - not init!\n");
		return;
	}

	c_item_header = USER_ITEM_HEADER_C(&g_user_password.instance, item_index);
	gkt_trace("user_dynpwd: delete_info - user_index(%u), item_index(%u)\n",
				c_item_header->user_index, item_index);
	if (c_item_header->authority.type != GKT_USER_AUTH_ADMIN)
		return;

	nums = g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN];
	gkt_trace("user_dynpwd: delete_info - nums = %u\n", nums);
	for (i = 0; i < nums; i++) {
		if ((uint32_t)g_user_dynpwd_info.admin_item_indexs[i] == item_index) {
			if (i < (nums - 1)) {
				g_user_dynpwd_info.admin_item_indexs[i] = 
					g_user_dynpwd_info.admin_item_indexs[nums - 1];
#if USER_DYNPWD_TOTP_ENABLE
				g_user_dynpwd_info.totp_last_steps[i] = 
					g_user_dynpwd_info.totp_last_steps[nums - 1];
#endif
				user_dynpwd_save_bpk_info(i);	
			}
			user_dynpwd_clear_info(nums - 1);

			break;
		}
	}
}
void user_dynpwd_reset_info(void)
{
	uint32_t i;

	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_warning("user_dynpwd: reset info - not init!\n");
		return;
	}

	gkt_trace("user_dynpwd: reset info ...\n");
	for (i = 0; i < GKT_USER_PASSWORD_ADMIN_MAXNUMS; i++)
		user_dynpwd_clear_info(i);
}

void user_dynpwd_reset_partition(void)
{
	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_warning("user_dynpwd: reset partition - not init!\n");
		return;
	}

	gkt_trace("user_dynpwd: reset partition ...\n");

	g_user_dynpwd.next_seq = 0;
	user_reset_partition(&g_user_dynpwd.instance);
}

int user_dynpwd_reload_items_v(void)
{
	uint32_t item_total_size;
	int retval = GKT_SUCCESS;

	if (!g_user_dynpwd.instance.c_flash_info->read_map_addr) {
		item_total_size = g_user_dynpwd.instance.item_size * g_user_dynpwd.instance.item_available_nums;

		if (!g_user_dynpwd.instance.v_items_base) {
			g_user_dynpwd.instance.v_items_base = 
				(user_item_header_u *)gkt_malloc(item_total_size);
			if (!g_user_dynpwd.instance.v_items_base) {
				gkt_fatal("dynpwd_reload_items_v: alloc memory failed.\n");
				return GKT_ENOMEMORY;
			}
		}

		retval = gkt_flash_partition_read(g_user_dynpwd.instance.c_flash_info->id,
						g_user_dynpwd.instance.item_base_offset, item_total_size,
						g_user_dynpwd.instance.v_items_base);
		if (retval != GKT_SUCCESS)
			gkt_fatal("dynpwd_reload_items_v: read items failed %d\n", retval);
	}

	return retval;
}

#define USER_DYNPWD_RECYCLE_MAXNUMS	4
static uint32_t user_dynpwd_recycle_cancelled_items(void)
{
	const user_dynpwd_item_s *c_dynpwd_item;
	uint32_t earliest_cancelled_item_nums;
	uint32_t earliest_cancelled_item_indexs[USER_DYNPWD_RECYCLE_MAXNUMS];
	uint32_t earliest_cancelled_item_seqs[USER_DYNPWD_RECYCLE_MAXNUMS];
	uint32_t dynpwd_item_index, latest_item_index, latest_item_seq, i;

	dynpwd_item_index = 0;
	earliest_cancelled_item_nums = 0;
	while (dynpwd_item_index < g_user_dynpwd.instance.item_available_nums) {
		c_dynpwd_item = USER_DYNPWD_ITEM_C(dynpwd_item_index);
		if (USER_ITEM_IS_STATE(&c_dynpwd_item->ext_header, USER_ITEM_ST_CANCELLED)) {
			if (USER_DYNPWD_RECYCLE_MAXNUMS == earliest_cancelled_item_nums) {
				latest_item_index = 0;
				latest_item_seq = earliest_cancelled_item_seqs[0];
				for (i = 1; i < USER_DYNPWD_RECYCLE_MAXNUMS; i++) {
					if (latest_item_seq < earliest_cancelled_item_seqs[i]) {
						latest_item_index = i;
						latest_item_seq = earliest_cancelled_item_seqs[i];
					}
				}

				if (c_dynpwd_item->ext_header.dynpwd_seq < latest_item_seq) {
					earliest_cancelled_item_indexs[latest_item_index] = dynpwd_item_index;
					earliest_cancelled_item_seqs[latest_item_index] = c_dynpwd_item->ext_header.dynpwd_seq;
				}
			}
			else {
				earliest_cancelled_item_indexs[earliest_cancelled_item_nums] = dynpwd_item_index;
				earliest_cancelled_item_seqs[earliest_cancelled_item_nums] = c_dynpwd_item->ext_header.dynpwd_seq;
				earliest_cancelled_item_nums++;
			}
		}

		dynpwd_item_index++;
	}

	for (i = 0; i < earliest_cancelled_item_nums; i++) {
		user_update_item_state(&g_user_dynpwd.instance, 
			earliest_cancelled_item_indexs[i], USER_ITEM_ST_DELETED);
	}

	return earliest_cancelled_item_nums;
}

static uint32_t user_dynpwd_get_free_item(void)
{
	const user_dynpwd_item_s *c_dynpwd_item;
	uint32_t dynpwd_item_index, unused_item_nums;
	int recycled, retval;

	recycled = 0;
retrieve_items:
	unused_item_nums= 0;
	dynpwd_item_index = 0;
	while (dynpwd_item_index < g_user_dynpwd.instance.item_available_nums) {
		c_dynpwd_item = USER_DYNPWD_ITEM_C(dynpwd_item_index);
		if (USER_ITEM_EXTEND_IS_FREE(&c_dynpwd_item->ext_header)) {
			gkt_trace("user_dynpwd: <FREE> %u\n", dynpwd_item_index);
			return dynpwd_item_index;
		}
		else if (USER_ITEM_IS_UNUSED(&c_dynpwd_item->ext_header))
			unused_item_nums++;

		dynpwd_item_index++;
	}

	if (!recycled) {
		if (!unused_item_nums) {
			unused_item_nums = user_dynpwd_recycle_cancelled_items();
			if (!unused_item_nums)
				goto done;
		}

		recycled = 1;
		retval = user_recycle(&g_user_dynpwd.instance, 0);
		if (GKT_SUCCESS == retval)
			goto retrieve_items;
	}

done:
	gkt_trace("user_dynpwd: *** no free item ***\n");
	return ~0;
}

static void user_dynpwd_init_next_seq(void)
{
	const user_dynpwd_item_s *c_dynpwd_item;
	uint32_t dynpwd_item_index;

	if (!g_user_dynpwd.next_seq) {
		dynpwd_item_index = 0;
		while (dynpwd_item_index < g_user_dynpwd.instance.item_available_nums) {
			c_dynpwd_item = USER_DYNPWD_ITEM_C(dynpwd_item_index);
			gkt_trace("user_dynpwd_next_seq: (%03u) user_index(%u) type(%u) state(%x) seq(%u)\n", 
					dynpwd_item_index, c_dynpwd_item->ext_header.user_index, 
					c_dynpwd_item->ext_header.dynpwd_type, c_dynpwd_item->ext_header.state, 
					c_dynpwd_item->ext_header.dynpwd_seq);

			if ((c_dynpwd_item->ext_header.user_index >= GKT_USER_INDEX_MIN)
				&& (c_dynpwd_item->ext_header.user_index <= GKT_USER_INDEX_MAX)
				&& (c_dynpwd_item->ext_header.dynpwd_type < USER_DYNPWD_TYPE_MAX)
				&& ((USER_ITEM_ST_USED == c_dynpwd_item->ext_header.state)
					|| (USER_ITEM_ST_CANCELLED == c_dynpwd_item->ext_header.state)))
			{
				if (g_user_dynpwd.next_seq < c_dynpwd_item->ext_header.dynpwd_seq)
					g_user_dynpwd.next_seq = c_dynpwd_item->ext_header.dynpwd_seq;
			}

			dynpwd_item_index++;
		}

		g_user_dynpwd.next_seq++;
		gkt_info("user_dynpwd_next_seq: %u\n", g_user_dynpwd.next_seq);
	}
}
int user_dynpwd_save_item(user_dynpwd_item_s *v_item,
			uint8_t state)
{
	uint32_t dynpwd_item_index;
	int retval;

	user_dynpwd_init_next_seq();
	v_item->ext_header.authority.type = GKT_USER_AUTH_NORMAL;
	v_item->ext_header.dynpwd_seq = g_user_dynpwd.next_seq++;

	dynpwd_item_index = user_dynpwd_get_free_item();
	if (dynpwd_item_index < g_user_dynpwd.instance.item_available_nums) {
		gkt_trace("user_dynpwd_save: (0x%03x-0x%03x) st(0x%02x) type(%u) seq(0x%06x)\n",
			dynpwd_item_index, v_item->ext_header.user_index, state,
			v_item->ext_header.dynpwd_type, v_item->ext_header.dynpwd_seq);
		retval = user_save_item(&g_user_dynpwd.instance, dynpwd_item_index, state);
	}
	else
		retval = GKT_ENORESOURCE;

	return retval;
}

void user_dynpwd_delete_item(uint32_t item_index)
{
	const user_item_header_u *c_item_header;
	const user_dynpwd_item_s *c_dynpwd_item;
	uint32_t dynpwd_item_index;
	int retval;

	gkt_trace("user_dynpwd: delete_item - item_index(%u)\n", item_index);
	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_error("user_dynpwd: delete_item - not init!\n");
		return;
	}

	c_item_header = USER_ITEM_HEADER_C(&g_user_password.instance, item_index);
	gkt_trace("user_dynpwd: delete_item - user_index(%u), item_index(%u)\n",
				c_item_header->user_index, item_index);
	if (c_item_header->authority.type != GKT_USER_AUTH_ADMIN)
		return;

	retval = user_dynpwd_reload_items_v();
	if (retval != GKT_SUCCESS) {
		gkt_error("user_dynpwd: delete_item - reload items_v failed %d\n", retval);
		return;
	}
	
	dynpwd_item_index = 0;
	while (dynpwd_item_index < g_user_dynpwd.instance.item_available_nums) {
		c_dynpwd_item = USER_DYNPWD_ITEM_C(dynpwd_item_index);
		if ((c_item_header->user_index == c_dynpwd_item->ext_header.user_index)
			&& (c_dynpwd_item->ext_header.state != USER_ITEM_ST_DELETED))
		{
			user_update_item_state(&g_user_dynpwd.instance, dynpwd_item_index, 
				USER_ITEM_ST_DELETED);
		}

		dynpwd_item_index++;
	}
}

int user_dynpwd_verify(uint32_t *user_id, 
				const uint8_t *digits, uint32_t digits_len)
{
	const user_dynpwd_instance_s *instance;
	user_dynpwd_parse_data_s parse_data;
	user_dynpwd_data_s data;
	int retval;

	if (!(g_user_dynpwd.instance.flags & USER_INSTANCE_F_INIT_OK)) {
		gkt_error("user_dynpwd_verify: not init!\n");
		return GKT_ENOTINIT;
	}
	
#if USER_DYNPWD_TOTP_ENABLE
	if (USER_DYNPWD_TOTP_SIZE == digits_len)
		return user_dynpwd_totp_verify(user_id, digits);
#endif

	if ((digits_len >= USER_DYNPWD_DIGITS_MINSIZE)
		&& (digits_len <= USER_DYNPWD_DIGITS_MAXSIZE))
	{
		retval = user_dynpwd_parse_digits(&parse_data, 
					(const char *)digits, digits_len);
		if (GKT_SUCCESS == retval) {
			gkt_trace("user_dynpwd: user_index(%u), units_size(%u)\n", 
				parse_data.user_index, parse_data.units_size);

			data.type = parse_data.values[0] & USER_DYNPWD_TYPE_MASK;
			data.cancel = (parse_data.values[0] & USER_DYNPWD_FLAG_CANCEL_MASK) ? 1 : 0;
			gkt_trace("user_dynpwd: type(%u), cancel(%d)\n", 
				data.type, data.cancel);

			if (data.type < USER_DYNPWD_TYPE_MAX) {
				retval = user_dynpwd_reload_items_v();
				if (retval != GKT_SUCCESS)
					return GKT_USER_VERIFY_FAILED;
				
				instance = &sc_user_dynpwd_instances[data.type];
				if (instance->units_size == parse_data.units_size) {
					if (!instance->retrieve)
						return GKT_USER_VERIFY_FAILED;

					retval = (*instance->retrieve)(&data, parse_data.values);
					if (GKT_SUCCESS == retval) {
						if (!instance->verify)
							return GKT_USER_VERIFY_FAILED;

						retval = (*instance->verify)(&data, parse_data.user_index);
						gkt_trace("user_dynpwd: verify - result(%d)\n", retval);
						if ((GKT_USER_VERIFY_PASSED == retval)
							|| (GKT_USER_VERIFY_CANCELLED == retval)) 
						{
							*user_id = GKT_USER_ID_BUILD_FULL(GKT_USER_AUTH_TEMPORARY, 0,
											GKT_USER_TYPE_PASSWORD, GKT_USER_PASSWORD_NOTS + data.type,
											parse_data.user_index);
						}

						return retval;
					}
				}
			}
		}
	}

	return GKT_USER_VERIFY_FAILED;
}

/************************************************************
* BPK Key:A total of 32 uint32_t 
*		  uint32_t [0~6]:Refer to gkt_retmem.h
*
*		  uint32_t [7]:user dynpwd info
*			   Byte[0]:
*			   Byte[1]:admin item indexs[pwd admin 0]
*			   Byte[2]:
*			   Byte[3]:admin item indexs[pwd admin 1]
*		  uint32_t [8]:user dynpwd info
*			   Byte[0]:
*			   Byte[1]:admin item indexs[pwd admin 2]
*			   Byte[2]:
*			   Byte[3]:admin item indexs[pwd admin 3]
*
*		  			   user dynpwd totp enable
*		  uint32_t [9]:totp_last_steps[password admin 0]
*		  uint32_t [10]:totp_last_steps[password admin 1]
*		  uint32_t [11]:totp_last_steps[password admin 2]
*		  uint32_t [12]:totp_last_steps[password admin 3]	
************************************************************/
int user_dynpwd_init(void)
{
	int retval;
	uint32_t i,j=0, nums, value;
	
	g_user_dynpwd.next_seq = 0;
	nums = g_user_password_info.auth_user_used_nums[GKT_USER_AUTH_ADMIN];
	for (i=0; i<nums; i++) {
		G_BPK_ReadKey(&value, 1, GKT_RETMEM_USER_DYNPWD_INFO0_1 + j);
		if ((i % 2) == 0)
			g_user_dynpwd_info.admin_item_indexs[i] = (value & 0xFFFF);
		else {
			j++;
			g_user_dynpwd_info.admin_item_indexs[i] = (value >> 16) & 0xFFFF;
		}
#if USER_DYNPWD_TOTP_ENABLE
		g_user_dynpwd_info.totp_last_steps[i] = G_BPK_ReadKey(&value, 1, i + GKT_RETMEM_USER_DYNPWD_ADMIN0_PWD);
#endif
	}
	
	retval = user_init(&g_user_dynpwd.instance);
	if (retval != GKT_SUCCESS)
		gkt_error("user_dynpwd_init: failed %d\n", retval);

	return retval;
}

